/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.interpreter.frontends.rdfxml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Laurent WOUTERS
 */
public class GraphLoader {
    private static final String defaultRDFGraphs = "http://www.xowl.org/interpreter/rdfgraphs/";
    private static final String prefix = "rdf:";
    private static final String rdfDescription = prefix + xowl.interpreter.frontends.VocRDF.nameDescription;
    private static final String rdfAbout = prefix + xowl.interpreter.frontends.VocRDF.nameAbout;
    private static final String rdfID = prefix + xowl.interpreter.frontends.VocRDF.nameID;
    private static final String rdfNodeID = prefix + xowl.interpreter.frontends.VocRDF.nameNodeID;
    private static final String rdfResource = prefix + xowl.interpreter.frontends.VocRDF.nameResource;
    private static final String rdfDatatype = prefix + xowl.interpreter.frontends.VocRDF.nameDatatype;
    private static final String rdfParseType = prefix + xowl.interpreter.frontends.VocRDF.nameParseType;
    private static final String rdfType = prefix + xowl.interpreter.frontends.VocRDF.nameType;
    private static final String rdfFirst = prefix + xowl.interpreter.frontends.VocRDF.nameFirst;
    private static final String rdfRest = prefix + xowl.interpreter.frontends.VocRDF.nameRest;
    private static final String rdfNil = prefix + xowl.interpreter.frontends.VocRDF.nameNil;
    
    private xowl.interpreter.frontends.OntologyListener listener;
    private xowl.interpreter.impl.AggregatedEvaluator evaluator;
    private xowl.interpreter.backends.rdf.RDFGraph graph;
    private Map<String, String> prefixes;
    private String baseIRI;
    private xowl.lang.owl2.Ontology ontology;
    private Map<String, xowl.interpreter.backends.rdf.RDFSubjectNode> anonymousNodes;

    public xowl.interpreter.backends.rdf.RDFGraph getGraph() { return graph; }
    public xowl.lang.owl2.Ontology getOntology() { return ontology; }

    public GraphLoader(xowl.interpreter.impl.AggregatedEvaluator evaluator, xowl.interpreter.frontends.OntologyListener listener, xowl.interpreter.backends.rdf.RDFGraph graph) {
        this.listener = listener;
        this.evaluator = evaluator;
        this.graph = graph;
        this.prefixes = new HashMap<String, String>();
        this.anonymousNodes = new HashMap<String, xowl.interpreter.backends.rdf.RDFSubjectNode>();
    }

    public void load(org.w3c.dom.Node node) throws xowl.interpreter.XOWLException {
        for (int i=0; i!=node.getAttributes().getLength(); i++) {
            String name = node.getAttributes().item(i).getNodeName();
            String value = node.getAttributes().item(i).getNodeValue();
            if (name.startsWith("xmlns:")) {
                prefixes.put(name.substring(6), value);
            } else if (name.equals("xmlns")) {
                setBaseIRI(value);
            }
        }
        if (baseIRI == null) {
            java.util.Random rand = new java.util.Random();
            String value = defaultRDFGraphs + Integer.toHexString(rand.nextInt()) + "#";
            setBaseIRI(value);
        }
        for (org.w3c.dom.Node child : getElements(node))
            loadNode(child);
    }
    private void setBaseIRI(String value) {
        baseIRI = value;
        ontology = listener.resolveOntology(listener.getIRI(baseIRI.substring(0, baseIRI.length() - 1)));
    }

    public static List<org.w3c.dom.Node> getElements(org.w3c.dom.Node node) {
        List<org.w3c.dom.Node> list = new ArrayList<org.w3c.dom.Node>();
        for (int i=0; i!=node.getChildNodes().getLength(); i++) {
            org.w3c.dom.Node child = node.getChildNodes().item(i);
            if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE)
                list.add(child);
        }
        return list;
    }

    private xowl.lang.owl2.IRI getIRIForNode(org.w3c.dom.Node node) {
        for (int i=0; i!=node.getAttributes().getLength(); i++) {
            String name = node.getAttributes().item(i).getNodeName();
            String value = node.getAttributes().item(i).getNodeValue();
            if (name.equals(rdfAbout)) {
                return getIRIForNode_GetIRI(value);
            } else if (name.equals(rdfID)) {
                return getIRIForNode_GetIRI(value);
            } else if (name.equals(rdfResource)) {
                return getIRIForNode_GetIRI(value);
            }
        }
        return null;
    }
    private xowl.lang.owl2.IRI getIRIForNode_GetIRI(String value) {
        if (value.contains("#")) {
            if (value.startsWith("#"))
                return listener.getIRI(baseIRI + value.substring(1));
            else
                return listener.getIRI(value);
        } else {
            return listener.getIRI(baseIRI + value);
        }
    }
    private xowl.lang.owl2.IRI getName(String name) {
        if (name.contains(":")) {
            String parts[] = name.split(":");
            return listener.getIRI(prefixes.get(parts[0]) + parts[1]);
        } else {
            return listener.getIRI(baseIRI + name);
        }
    }

    private xowl.interpreter.backends.rdf.RDFSubjectNode loadNode(org.w3c.dom.Node node) throws xowl.interpreter.XOWLException {
        xowl.interpreter.backends.rdf.RDFSubjectNode subject = null;
        String name = node.getNodeName();
        xowl.lang.owl2.IRI iri = getIRIForNode(node);
        if (iri != null)
            subject = graph.getEntityNode(iri);
        else {
            org.w3c.dom.Node attID = node.getAttributes().getNamedItem(rdfNodeID);
            if (attID != null) {
                String valueID = attID.getNodeValue();
                if (anonymousNodes.containsKey(valueID))
                    subject = anonymousNodes.get(valueID);
                else {
                    subject = createAnonymous(valueID);
                    anonymousNodes.put(valueID, subject);
                }
            } else
                subject = createAnonymous();
        }

        if (name.equals(rdfDescription)) {
            // Found untyped node
        } else {
            // Found typed node
            addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, graph.getEntityNode(getName(rdfType)), graph.getEntityNode(getName(name))));
        }

        // Load properties expressed in node attributes
        for (int i=0; i!=node.getAttributes().getLength(); i++) {
            String attName = node.getAttributes().item(i).getNodeName();
            String attValue = node.getAttributes().item(i).getNodeValue();
            if (attName.equals(rdfAbout)) { }
            else if (attName.equals(rdfID)) { }
            else if (attName.equals(rdfResource)) { }
            else loadProperty(subject, attName, attValue);
        }

        // Load property nodes
        for (org.w3c.dom.Node child : getElements(node))
            loadProperty(subject, child);
        return subject;
    }
    private xowl.interpreter.backends.rdf.RDFSubjectNode loadBlankNode(org.w3c.dom.Node node) throws xowl.interpreter.XOWLException {
        xowl.interpreter.backends.rdf.RDFSubjectNode subject = createAnonymous();
        // Load property nodes
        for (org.w3c.dom.Node child : getElements(node))
            loadProperty(subject, child);
        return subject;
    }
    private void loadProperty(xowl.interpreter.backends.rdf.RDFSubjectNode subject, org.w3c.dom.Node node) throws xowl.interpreter.XOWLException {
        String name = node.getNodeName();
        xowl.interpreter.backends.rdf.RDFIRIReference property = graph.getEntityNode(getName(name));
        // Check attributes
        for (int i=0; i!=node.getAttributes().getLength(); i++) {
            String attName = node.getAttributes().item(i).getNodeName();
            if (attName.equals(rdfAbout) || attName.equals(rdfID) || attName.equals(rdfResource)) {
                // the value of the property is an xowl.interpreter.backends.rdf.RDF node in attribute
                xowl.lang.owl2.IRI iri = getIRIForNode(node);
                if (iri != null) {
                    xowl.interpreter.backends.rdf.RDFNode object = graph.getEntityNode(iri);
                    addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, property, object));
                    return;
                }
            } else if (attName.equals(rdfParseType)) {
                String attValue = node.getAttributes().item(i).getNodeValue();
                if (attValue.equals("Literal")) {
                    // Value is an XML Literal
                } else if (attValue.equals("Collection")) {
                    // Value is a collection
                    List<xowl.interpreter.backends.rdf.RDFNode> values = new ArrayList<xowl.interpreter.backends.rdf.RDFNode>();
                    List<xowl.interpreter.backends.rdf.RDFSubjectNode> proxies = new ArrayList<xowl.interpreter.backends.rdf.RDFSubjectNode>();
                    for (org.w3c.dom.Node child : getElements(node)) {
                        xowl.interpreter.backends.rdf.RDFNode value = loadNode(child);
                        xowl.interpreter.backends.rdf.RDFSubjectNode proxy = graph.getBlankNode();
                        values.add(value);
                        proxies.add(proxy);
                        addTriple(new xowl.interpreter.backends.rdf.RDFTriple(proxy, graph.getEntityNode(getName(rdfFirst)), value));
                    }
                    if (values.isEmpty()) {
                        addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, property, graph.getEntityNode(getName(rdfNil))));
                    } else {
                        addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, property, proxies.get(0)));
                        for (int n=0; n!=proxies.size()-1; n++)
                            addTriple(new xowl.interpreter.backends.rdf.RDFTriple(proxies.get(n), graph.getEntityNode(getName(rdfRest)), proxies.get(n+1)));
                        addTriple(new xowl.interpreter.backends.rdf.RDFTriple(proxies.get(proxies.size() - 1), graph.getEntityNode(getName(rdfRest)), graph.getEntityNode(getName(rdfNil))));
                    }
                    return;
                } else if (attValue.equals("Resource")) {
                    // Anonymous node
                    xowl.interpreter.backends.rdf.RDFNode object = loadBlankNode(node);
                    addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, property, object));
                }
            } else if (attName.equals(rdfDatatype)) {
                String attValue = node.getAttributes().item(i).getNodeValue();
                xowl.lang.runtime.Literal literal = new xowl.lang.runtime.Literal();
                literal.setLexicalValue(node.getTextContent());
                literal.setMemberOf(listener.getDatatype(attValue));
                xowl.interpreter.backends.rdf.RDFNode object = new xowl.interpreter.backends.rdf.RDFLiteralNode(literal);
                addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, property, object));
                return;
            }
        }

        // Property value is a single node
        List<org.w3c.dom.Node> children = getElements(node);
        xowl.interpreter.backends.rdf.RDFNode object = null;
        if(children.isEmpty()) {
            // Plain literal value
            xowl.lang.runtime.Literal literal = new xowl.lang.runtime.Literal();
            literal.setLexicalValue(node.getTextContent());
            literal.setMemberOf(listener.getDatatype(xowl.interpreter.impl.OWLDatatype.xsdString));
            object = new xowl.interpreter.backends.rdf.RDFLiteralNode(literal);
        } else {
            // Value is an xowl.interpreter.backends.rdf.RDF node
            object = loadNode(getElements(node).get(0));
        }
        addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, property, object));
    }
    private void loadProperty(xowl.interpreter.backends.rdf.RDFSubjectNode subject, String name, String value) throws xowl.interpreter.XOWLException {
        xowl.lang.owl2.IRI property = getName(name);
        xowl.lang.runtime.Literal literal = new xowl.lang.runtime.Literal();
        literal.setLexicalValue(value);
        literal.setMemberOf(listener.getDatatype(xowl.interpreter.impl.OWLDatatype.xsdString));
        xowl.interpreter.backends.rdf.RDFNode object = new xowl.interpreter.backends.rdf.RDFLiteralNode(literal);
        addTriple(new xowl.interpreter.backends.rdf.RDFTriple(subject, graph.getEntityNode(property), object));
    }
    
    private xowl.interpreter.backends.rdf.RDFBlankNode createAnonymous() {
        return graph.getBlankNode(new xowl.lang.owl2.AnonymousIndividual());
    }
    private xowl.interpreter.backends.rdf.RDFBlankNode createAnonymous(String nodeID) {
        xowl.lang.owl2.AnonymousIndividual ind = new xowl.lang.owl2.AnonymousIndividual();
        ind.setNodeID(nodeID);
        return graph.getBlankNode(ind);
    }
    
    private void addTriple(xowl.interpreter.backends.rdf.RDFTriple triple) {
        graph.onChange(evaluator, new xowl.interpreter.backends.rdf.RDFChange(triple, ontology, true));
    }
}
